﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Axial
{
	public class WhileCondition : CodeSegment
	{
		public string Condition { get; private set; }
		public int ConditionOffset { get; private set; }
		public int CodeOffset { get; private set; }
		public int EndOffset { get; private set; }

		public CodeSegment CodeSegment { get; private set; }

		public WhileCondition(IEnumerable<CodeStatement> enumStatements)
		{
			List<CodeStatement> statements = enumStatements.ToList();

			if (!(statements.First() is JumpStatement))
				throw new ArgumentException("A WhileCondition must start with a Jump CodeSegment");
			ConditionOffset = (statements.First() as JumpStatement).FlowTarget;
			CodeOffset = statements.ElementAt(1).StartOffset;
			// the end is after the conditions
			var endStatement = statements.SkipWhile(s => s.StartOffset < ConditionOffset)
										 .SkipWhile(s => s is ConditionStatement)
										 .FirstOrDefault();
			EndOffset = endStatement != null ? endStatement.StartOffset : int.MaxValue;

			// determine the condition
			var conditions = statements.SkipWhile(s => s.StartOffset < ConditionOffset).TakeWhile(s => s.StartOffset < EndOffset).Cast<ConditionStatement>();
			foreach (var condition in conditions)
				if (condition.FlowTarget >= CodeOffset && condition.FlowTarget < ConditionOffset)
					Condition += condition.Condition + " || ";
				else
					Condition += ConditionHelper.InvertCondition(condition.Condition) + " && ";
			Condition = Condition.Substring(0, Condition.Length - 4);

			// HACK: handle special case - outer while condition success goes to inner while condition
			// so there's no jump at the top of the inner while
			List<CodeStatement> codeStatements;
			var upjump = statements.Single(s => s.StartOffset == conditions.Last().FlowTarget) as ConditionStatement;
			if (upjump != null && upjump.FlowTarget < upjump.StartOffset &&
					!statements.Any(s => s.StartOffset > CodeOffset && s.StartOffset < upjump.FlowTarget &&
					s is JumpStatement && (s as JumpStatement).FlowTarget == upjump.StartOffset))
			{
				statements.Insert(statements.IndexOf(statements.Find(s => s.StartOffset == upjump.FlowTarget)),
					CodeStatement.Create(
						new ILInstruction[] { 
							new ILInstruction
							{ 
								Code = System.Reflection.Emit.OpCodes.Br_S, 
								Offset = upjump.FlowTarget - 1,
								Operand = upjump.StartOffset
							}
						},
						upjump.Parameters));
				codeStatements = statements.SkipWhile(s => s.StartOffset < CodeOffset - 1).TakeWhile(s => s.StartOffset < ConditionOffset).ToList();
			}
			else
			{
				codeStatements = statements.SkipWhile(s => s.StartOffset < CodeOffset).TakeWhile(s => s.StartOffset < ConditionOffset).ToList();
			}

			// fix breaks and continues
			var jumps = codeStatements.Where(r => r.GetType() == typeof(JumpStatement)).Cast<JumpStatement>().ToArray();
			foreach (var jump in jumps)
			{
				int index = codeStatements.IndexOf(jump);
				if (jump.FlowTarget == EndOffset)
					codeStatements[index] = new BreakStatement(jump.StartOffset);
				if (jump.FlowTarget == ConditionOffset)
					codeStatements[index] = new ContinueStatement(jump.StartOffset);
				if (jump.FlowTarget > EndOffset)
					codeStatements[index] = new ReturnStatement(jump.StartOffset);
			}

			// fix breaks and continues in if statements
			for (int index = 0; index < codeStatements.Count; ++index)
			{
				ConditionStatement condition = codeStatements[index] as ConditionStatement;
				if (condition == null)
					continue;

				if (condition.FlowTarget >= EndOffset)
				{
					codeStatements[index] = new BreakConditionStatement(condition);
					for (int i = index - 1; i >= 0; --i)
					{
						if (codeStatements[i].GetType() == typeof(ConditionStatement))
							codeStatements[i] = new BreakConditionStatement(codeStatements[i]);
						else
							break;
					}
				}
				if (condition.FlowTarget == ConditionOffset)
				{
					codeStatements[index] = new ContinueConditionStatement(condition);
					for (int i = index - 1; i >= 0; --i)
					{
						if (codeStatements[i].GetType() == typeof(ConditionStatement))
							codeStatements[i] = new ContinueConditionStatement(codeStatements[i]);
						else
							break;
					}
				}
			}

			CodeSegment = new CodeSegment(codeStatements);
		}

		public override string ToString()
		{
			return string.Format("while ({0})\n{{\n{1}}}\n", Condition, CodeSegment);
		}

		public override JSWriter ToJSWriter()
		{
			JSWriter writer = new JSWriter();
			writer.AddWhileBlock(Condition, CodeSegment.ToJSWriter());
			return writer;
		}
	}
}
